#include "stdafx.h"
#include "afxinet.h"

#include "charplan.h"
#include "resource.h"
#include "updatecheck.h"
#include "dlgupdate.h"

#include "shlwapi.h"



//#define SIMULATE


DWORD GetDllVersion(LPCTSTR lpszDllName)
{
    HINSTANCE hinstDll;
    DWORD dwVersion = 0;

    /* For security purposes, LoadLibrary should be provided with a
    fully-qualified path to the DLL. The lpszDllName variable should be
    tested to ensure that it is a fully qualified path before it is used. */
    hinstDll = LoadLibrary(lpszDllName);

    if (hinstDll)
    {
        DLLGETVERSIONPROC pDllGetVersion;
        pDllGetVersion = (DLLGETVERSIONPROC)GetProcAddress(hinstDll,
                         "DllGetVersion");

        /* Because some DLLs might not implement this function, you
        must test for it explicitly. Depending on the particular
        DLL, the lack of a DllGetVersion function can be a useful
        indicator of the version. */
        if (pDllGetVersion)
        {
            DLLVERSIONINFO dvi;
            HRESULT hr;

            ZeroMemory(&dvi, sizeof(dvi));
            dvi.cbSize = sizeof(dvi);

            hr = (*pDllGetVersion)(&dvi);

            if (SUCCEEDED(hr))
            {
                dwVersion = dvi.dwMajorVersion;
            }
        }

        FreeLibrary(hinstDll);
    }
    return dwVersion;
}


HWND m_main_hwnd = NULL;

void ShowTrayIcon(LPCTSTR text, bool show = true)
{
    if (text== NULL || !show)
    {
        NOTIFYICONDATA nid;
        nid.cbSize = sizeof(nid);
        nid.hWnd = m_main_hwnd;
        nid.uID = 1;
        Shell_NotifyIcon(NIM_DELETE, &nid);
        m_main_hwnd = NULL;
        return;
    }

    DWORD mode = m_main_hwnd?NIM_MODIFY:NIM_ADD;



    // Maik Jurkait [2/11/2007]
    // ------------------------
    // find the main window
    // ------------------------
    DWORD dw_timer = 20;
    while (!m_main_hwnd && (m_main_hwnd = AfxGetApp()->GetMainWnd()->GetSafeHwnd()) == NULL && dw_timer>0)
        { dw_timer--; Sleep(100); }
    if (!m_main_hwnd) return;


    // Maik Jurkait [2/11/2007]
    // ------------------------
    // Setup ICON
    // ------------------------
    DWORD ver = GetDllVersion("Shell32.dll");

    if (ver < 5)
    {
        NOTIFYICONDATA nid;
        nid.cbSize = sizeof(nid);
        nid.hWnd = m_main_hwnd;
        nid.uID = 1;
        _tcsncpy_s(nid.szTip,64, text, 64);
        nid.hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

        nid.uFlags = NIF_ICON|NIF_TIP;
        Shell_NotifyIcon(mode, &nid);
    }
    else
    {
        // taken from ShellApi (since it's not supported under win98)
        // but we want "text"
        typedef struct _NOTIFYICONDATAA
        {
            DWORD cbSize;
            HWND hWnd;
            UINT uID;
            UINT uFlags;
            UINT uCallbackMessage;
            HICON hIcon;
            CHAR   szTip[128];
            DWORD dwState;
            DWORD dwStateMask;
            CHAR   szInfo[256];
            union
            {
                UINT  uTimeout;
                UINT  uVersion;  // used with NIM_SETVERSION, values 0, 3 and 4
            } DUMMYUNIONNAME;
            CHAR   szInfoTitle[64];
            DWORD dwInfoFlags;
        } NOTIFYICONDATA;

        NOTIFYICONDATA nid;
        nid.cbSize = sizeof(NOTIFYICONDATA);
        nid.hWnd = m_main_hwnd;
        nid.uID = 1;
        _tcsncpy_s(nid.szTip,64, text, 64);
        nid.hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

        nid.uFlags = NIF_ICON | NIF_TIP | 0x00000010 /*NIF_INFO*/;
        _tcsncpy_s(nid.szInfo,256, text, 256);
        nid.uTimeout = 5000;
        _tcsncpy_s(nid.szInfoTitle,64, _T("Charplan"), 64);
        nid.dwInfoFlags = 0;

        Shell_NotifyIcon(mode, (PNOTIFYICONDATAA)&nid);
    }
}




//////////////////////////////////////////////////////////////////////////
CUpdateCheck::CUpdateCheck()
{
    mp_running_thread = NULL;
    mb_panic_break = false;
    mstr_final_run.Empty();
    m_main_hwnd = 0;
}



//////////////////////////////////////////////////////////////////////////
CUpdateCheck::~CUpdateCheck()
{
    ShowTrayIcon(NULL,false);

    if (mp_running_thread)
    {
        mb_panic_break = true;
        WaitForSingleObject(mp_running_thread->m_hThread,60000);
    }

    if (!mstr_final_run.IsEmpty())
    {
#ifndef SIMULATE
        ShellExecute(NULL,_T("open"),mstr_final_run,mstr_final_run_params,NULL,SW_SHOWNORMAL);
#endif
    }
}



//////////////////////////////////////////////////////////////////////////
void CUpdateCheck::RunAutomatical()
{
    // Maik Jurkait [11/16/2006]
    // ------------------------
    // Check for new Version Message
    // ------------------------
    int newv = _tstoi(AfxGetApp()->GetProfileString(_T("Settings"),_T("new_version"),_T("0")));

    if (newv>0)
    {
        switch (newv)
        {
            case 0: // no new version or was installed manual
                break;
            case 1: // new version was installed silent
                AfxMessageBox(IDS_NEWVERSIONAVAILABLE);
                AfxGetApp()->WriteProfileString(_T("Settings"),_T("new_version"),_T("0"));
                break;

                // not used:  case 2: // download ok but not installed
        }

        // if error count > 13 disable autoupdate
        if (newv > 13)
        {
            if (AfxMessageBox(IDS_ERROR_ON_SILENT_UPDATE,MB_YESNO) == IDYES)
            {
                SETOPTION(_T("UPDATE_SILENT"),0);
            };

            AfxGetApp()->WriteProfileString(_T("Settings"),_T("new_version"),_T("0"));
        }
    }


    // Maik Jurkait [11/16/2006]
    // ------------------------
    // Start update-check
    // ------------------------
    ASSERT(!mp_running_thread);
    mp_running_thread = AfxBeginThread(Threadfunction,this);
}



//////////////////////////////////////////////////////////////////////////
void CUpdateCheck::DoModal()
{
    if (mp_running_thread)
    {
        AfxMessageBox(IDS_AUTOUPDATEISRUNNING);
        return;
    }


    CString downloadpath;
    DWORD   dw_version;
    if (CheckIfNewVersionAvailable(downloadpath,dw_version))
    {
        CDlgUpdate dia;

        switch (dia.DoModal(dw_version))
        {
            case 0:
                return;
            case 1:
            {
                int newv = _tstoi(AfxGetApp()->GetProfileString(_T("Settings"),_T("new_version"),_T("0")));
                newv = max(3,newv+1);
                CString temp;
                temp.Format(_T("%i"),newv);
                AfxGetApp()->WriteProfileString(_T("Settings"),_T("new_version"),temp);

                DownloadAndInstall(downloadpath);
            }
            break;

            case 2:
                ShellExecute(NULL,_T("open"),downloadpath,NULL,NULL,SW_SHOWNORMAL);
                break;
            case 3:
                ShellExecute(NULL,_T("open"),_T("http://daoctb.sourceforge.net"),NULL,NULL,SW_SHOWNORMAL);
                break;
        }

        SetCheckedForUpdate();
    }
    else
    {
        AfxMessageBox(IDS_NO_NEW_VERSION);
        SetCheckedForUpdate();
    }
}



//////////////////////////////////////////////////////////////////////////
bool  CUpdateCheck::DownloadAndInstall(CString& url)
{
    ShowTrayIcon(_T("Downloading new version"));


    // Maik Jurkait [11/16/2006]
    // ------------------------
    // get filename
    // ------------------------
    CString filename(_T("charplan_install.exe"));
    int p = url.Find(_T(".exe"));
    if (p>0)
    {
        CString temp = url.Left(p+4);
        while ((p=temp.FindOneOf(_T("/\\")))>=0)
            temp = temp.Mid(p+1);

        if (temp.GetLength()>4) filename = temp;
    }

    TCHAR temppath[MAX_PATH];
    GetTempPath(MAX_PATH,temppath);

    filename = CString(temppath) + filename;


    // Maik Jurkait [11/16/2006]
    // ------------------------
    // check for write access
    // ------------------------
    FILE *outf;
    if (fopen_s(&outf,filename,"wb"))  { ASSERT(false); return false; };
    fclose(outf);

    // Maik Jurkait [11/16/2006]
    // ------------------------
    // Download
    // ------------------------
#ifndef SIMULATE
    bool res = DownloadFile(url,filename);
    ASSERT(res);
    if (!res) return false;
#else
    Sleep(5000);
#endif


    // Maik Jurkait [2/11/2007]
    // ------------------------
    // Update icon
    // ------------------------
    ShowTrayIcon(_T("Close Charplan for install"));

    // Maik Jurkait [11/16/2006]
    // ------------------------
    //  prepare execution
    // ------------------------
    //AfxGetApp()->WriteProfileString(_T("Settings"),_T("new_version"),_T("2"));
    mstr_final_run = filename;
    mstr_final_run_params = _T("/S");

    return true;
}




//////////////////////////////////////////////////////////////////////////
UINT __cdecl CUpdateCheck::Threadfunction(LPVOID pParam)
{
    CUpdateCheck* p_class = (CUpdateCheck*)pParam;

#ifndef SIMULATE
    if (p_class->IsItTimeForUpdateCheck())
#else
    if (true)
#endif
    {
        CString downloadpath;
        DWORD dw_version;
        if (p_class->CheckIfNewVersionAvailable(downloadpath,dw_version))
        {

            if (GETOPTIONDEF(_T("UPDATE_SILENT"),0))
            {
                int newv = _tstoi(AfxGetApp()->GetProfileString(_T("Settings"),_T("new_version"),_T("0")));
                newv = max(3,newv+1);
                CString temp;
                temp.Format(_T("%i"),newv);
                AfxGetApp()->WriteProfileString(_T("Settings"),_T("new_version"),temp);

                p_class->DownloadAndInstall(downloadpath);
                p_class->SetCheckedForUpdate();
            }
            else
            {
                CDlgUpdate dia;

                switch (dia.DoModal(dw_version))
                {
                    case 0:
                        break;

                    case 1:
                        p_class->DownloadAndInstall(downloadpath);
                        p_class->SetCheckedForUpdate();
                        break;
                    case 2:
                        ShellExecute(NULL,_T("open"),downloadpath,NULL,NULL,SW_SHOWNORMAL);
                        p_class->SetCheckedForUpdate();
                        break;
                    case 3:
                        ShellExecute(NULL,_T("open"),_T("http://daoctb.sourceforge.net"),NULL,NULL,SW_SHOWNORMAL);
                        p_class->SetCheckedForUpdate();
                        break;
                }
            }
        }
        else
        {
            p_class->SetCheckedForUpdate();
        }
    }


    TRACE(_T("CUpdateCheck: finished"));
    p_class->mp_running_thread = NULL;

    return 1;
}




//////////////////////////////////////////////////////////////////////////
void CUpdateCheck::SetCheckedForUpdate()
{
    CTime curtime = CTime::GetCurrentTime();

    AfxGetApp()->WriteProfileString(_T("Settings"),_T("lastdate"),curtime.Format(_T("%Y-%m-%d %H:%M")));

}





//////////////////////////////////////////////////////////////////////////
bool CUpdateCheck::IsItTimeForUpdateCheck()
{
    // M.Jurkait [5/2/2006]
    // -------------------------
    // Date check
    // -------------------------
    CTime curtime = CTime::GetCurrentTime();
    CString last_time = AfxGetApp()->GetProfileString(_T("Settings"),_T("lastdate"),_T("1970-11-25 17:04"));

    if (last_time.Left(1)==_T("f") || last_time.Left(1)==_T("n"))
    {
        TRACE(_T("update check disabled\n"));
        return false;
    }

    CTime last_check(_tstoi(last_time.Left(4)),   // Year
                     _tstoi(last_time.Mid(5,2)), // Month
                     _tstoi(last_time.Mid(8,2)), // day
                     _tstoi(last_time.Mid(11,2)), // hour
                     _tstoi(last_time.Mid(14,2)), // min
                     0
                    );

    CTimeSpan update_interval(7,0,0,0);
    switch (GETOPTION(_T("UPDATE_CHECK")))
    {
        case 0:  /* unset */
            break;
        case 1:
            TRACE(_T("update check disabled (by option)\n"));
            return false;
        case 2:
            update_interval = CTimeSpan(7,0,0,0);
            break;
        case 3:
            update_interval = CTimeSpan(14,0,0,0);
            break;
        case 4:
            update_interval = CTimeSpan(31,0,0,0);
            break;
    }


    // check every 7 days
    if (curtime-last_check < update_interval)
    {
        TRACE(_T("to early to check for update\n"));
        return false;
    }

    return true;
}





//////////////////////////////////////////////////////////////////////////
bool CUpdateCheck::CheckIfNewVersionAvailable(CString& downloadpath,DWORD& new_version)
{

#ifdef FAKE_DOWNLOAD
    new_version = FAKE_VERSION;
    downloadpath = FAKE_INSTALL;
    return true;
#endif

    // M.Jurkait [5/1/2006]
    // -------------------------
    //  read http-page
    // -------------------------
    CInternetSession c_internet;
    CHttpConnection  *pc_http_connect;
    CHttpFile   *pc_http_file;

    TCHAR* read_buffer;
    UINT readed;

    CString    str_page(_T("daoctb.sourceforge.net"));
    CString    str_options(_T("Welcome.htm"));
    CString    search_for(_T("DAoC-Charplan v"));
    downloadpath=_T("http://sourceforge.net/project/showfiles.php?group_id=129011&package_id=141801");

    //  Open + Get Page
    try
    {
        pc_http_connect = c_internet.GetHttpConnection(str_page, (INTERNET_PORT)80);
    }
    catch (...)
    {
        TRACE(_T("can't connect to daoctb.sourceforge.net"));
        return 0;
    }

    pc_http_file =  pc_http_connect->OpenRequest(CHttpConnection::HTTP_VERB_GET, str_options);

    // get File
    try
    {
        pc_http_file->SendRequest();
    }
    catch (...)
    {
        c_internet.Close();
        delete(pc_http_file);
        delete(pc_http_connect);
        TRACE(_T("can't open page"));
        return 0;
    }

    // create buffer
#define BUFFER_SIZE 16000
    read_buffer = new TCHAR [BUFFER_SIZE];

    DWORD dw_pos = 0;
    // Read data
    TRY
    {
        do
        {
            // TODO: check if this is unicode compatible
            readed = pc_http_file->Read((void*)&read_buffer[dw_pos], BUFFER_SIZE-1- dw_pos);
            dw_pos += readed;

            if (mb_panic_break)
            {
                pc_http_file->Close();
                pc_http_connect->Close();
                c_internet.Close();
                delete(pc_http_file);
                delete(pc_http_connect);
                delete(read_buffer);
                return false;
            }

        }
        while (readed != 0 && dw_pos < BUFFER_SIZE);

        read_buffer[dw_pos] = 0;
    }
    CATCH_ALL(error)
    {
        read_buffer[dw_pos] = 0;
    }
    END_CATCH_ALL;

    // close all
    pc_http_file->Close();
    pc_http_connect->Close();
    c_internet.Close();
    delete(pc_http_file);
    delete(pc_http_connect);


    // M.Jurkait [5/1/2006]
    // -------------------------
    //  extract Version
    // -------------------------
    TCHAR* pos = _tcsstr(read_buffer,search_for);
    if (pos==NULL) { delete(read_buffer); return 0;}

    pos += _tcslen(search_for);
    TCHAR* maxpos = _tcschr(pos,_T('<'));

    WORD num[4];
    num[0]=num[1]=num[2]=num[3]=0;

    for (int t=0;t<4;t++)
    {
        num[t]= _tstoi(pos);
        pos = _tcschr(pos,_T('.'));
        if (!pos || pos>maxpos)
        {
            if (maxpos && isalpha(*(maxpos-1))) num[3] = *(maxpos-1)-_T('a')+1;
            break;
        }
        pos++;
    }

    // Maik Jurkait [11/15/2006]
    // ------------------------
    // find download address
    // ------------------------
    pos = _tcsstr(maxpos,_T("http:"));

    if (pos)
    {
        TCHAR* maxpos = _tcschr(pos,_T('<'));
        if (maxpos)
        {
            *maxpos = 0;
            downloadpath = pos;
        }
    }

    delete(read_buffer);



    // M.Jurkait [5/1/2006]
    // -------------------------
    // Get current version
    // -------------------------
    bool b_new_version_availabe = false;

    DWORD lo,hi;
    if (((CCharplanApp*)AfxGetApp())->GetProgramVersion(hi,lo))
    {
        if (HIWORD(hi)<num[0]) b_new_version_availabe = true;
        else
        if (HIWORD(hi)==num[0])
        {
            if (LOWORD(hi)<num[1]) b_new_version_availabe = true;
            else
            if (LOWORD(hi)==num[1])
            {
                if (HIWORD(lo)<num[2]) b_new_version_availabe = true;
                else
                if (HIWORD(lo)==num[2])
                {
                    if (LOWORD(lo)<num[3]) b_new_version_availabe = true;
                }
            }
        }
    }


// TEST TEMP
    //downloadpath = _T("http://switch.dl.sourceforge.net/sourceforge/daoctb/charplan161_install.exe");
    //downloadpath = _T("http://prdownloads.sourceforge.net/daoctb/charplan1621_install.exe?download");
    //b_new_version_availabe = true;

    new_version= ((num[0]&0xff)<<24) | ((num[1]&0xff)<<16) | ((num[2]&0xff)<<8) | ((num[3]&0xff));

    return b_new_version_availabe;
}




//////////////////////////////////////////////////////////////////////////
bool CUpdateCheck::DownloadFile(CString& url, const CString& filename)
{
#ifdef FAKE_INSTALL
    return false;
#endif

    CInternetSession c_internet;
    CHttpConnection  *pc_http_connect;
    CHttpFile   *pc_http_file;

    CString    str_page;
    CString    str_options;

    // Maik Jurkait [11/15/2006]
    // ------------------------
    // split url
    // ------------------------
    url.Replace('\\','/');

    int p1 = url.Find("//");
    if (p1 <0) p1 = 0;
    else
    {
        ASSERT(p1==5);
        ASSERT(url.Left(p1)==_T("http:"));
        p1 +=2;
    }

    int p2 = url.Find('/',p1);
    ASSERT(p2>0);
    if (p2<0) return false;

    str_page = url.Mid(p1,p2-p1);
    str_options = url.Mid(p2+1);



    //  Open + Get Page
    try
    {
        pc_http_connect = c_internet.GetHttpConnection(str_page, (INTERNET_PORT)80);
    }
    catch (...)
    {
        TRACE(_T("can't connect to daoctb.sourceforge.net"));
        return false;
    }

    pc_http_file =  pc_http_connect->OpenRequest(CHttpConnection::HTTP_VERB_GET, str_options);

    // get File
    try
    {
        pc_http_file->SendRequest();
    }
    catch (...)
    {
        c_internet.Close();
        delete(pc_http_file);
        delete(pc_http_connect);
        TRACE(_T("can't open page"));
        return false;
    }

    // Maik Jurkait [11/15/2006]
    // ------------------------
    // Create outputfile
    // ------------------------
    FILE* outf;
    if (fopen_s(&outf,filename,_T("wb")))
    {
        pc_http_file->Close();
        pc_http_connect->Close();
        c_internet.Close();
        delete(pc_http_file);
        delete(pc_http_connect);

        return false;
    }


    UCHAR read_buffer[0x8000];
    UINT  readed;

    // Read data
    TRY
    {
        do
        {
            readed = pc_http_file->Read((void*)&read_buffer, 0x8000-1);
            if (readed>0) fwrite(read_buffer,sizeof(UCHAR),readed,outf);

            if (mb_panic_break)
            {
                fclose(outf);
                DeleteFile(filename);
                pc_http_file->Close();
                pc_http_connect->Close();
                c_internet.Close();
                delete(pc_http_file);
                delete(pc_http_connect);
                return false;
            }

        }
        while (readed != 0);


    }
    CATCH_ALL(error)
    {
        fclose(outf);
        pc_http_file->Close();
        pc_http_connect->Close();
        c_internet.Close();
        delete(pc_http_file);
        delete(pc_http_connect);
        return false;
    }
    END_CATCH_ALL;

    // close all
    fclose(outf);

    pc_http_file->Close();
    pc_http_connect->Close();
    c_internet.Close();
    delete(pc_http_file);
    delete(pc_http_connect);

    return true;
}




